import * as fabric from 'fabric'

const customProps = ['relationship', 'bossId', 'isMinion', 'id', 'isBoss', 'minionIds']
const globalBossIds = new Set()
const globalMinionIds = new Set()
const getUniqueBossId = () => {
  let newID
  do {
    newID = Math.floor(Math.random() * 10 ** 5)
  } while (globalBossIds.has(newID))
  globalBossIds.add(newID)
  return newID
}
const getUniqueMinionId = () => {
  let newID
  do {
    newID = Math.floor(Math.random() * 10 ** 5)
  } while (globalMinionIds.has(newID))
  globalMinionIds.add(newID)
  return newID
}

// i m looking for the UNKNOW relation matrix where:
// BOSS * UNKNOW = MINION
// i multiply left for BOSS-INV
// BOSS-INV * BOSS * UNKNOW = BOSS-INV * MINION
// BOSS-INV * BOSS = IDENTIY, a neutral matrix.
// IDENTITY * UNKNOW = BOSS-INV * MINION
// so...
// UNKNOW = BOSS-INV * MINION
/**
 *  更新小兵追随领主
 * @param {fabric.FabricObject} boss
 * @param {fabric.FabricObject[]} minions
 * @param {Object} options
 * @param {boolean} [options.scale=false] 是否跟随缩放,默认false
 */
const updateMinions = (boss, minions, { scale = false } = {}) => {
  for (const minion of minions) {
    if (!minion.relationship) continue
    const { relationship } = minion
    const bossTransform = boss.calcTransformMatrix()
    const newTransform = fabric.util.multiplyTransformMatrices(bossTransform, relationship)
    const opt = fabric.util.qrDecompose(newTransform)
    minion.set({
      flipX: false,
      flipY: false,
    })
    minion.setPositionByOrigin({ x: opt.translateX, y: opt.translateY }, 'center', 'center')
    if (!scale) {
      const { scaleX, scaleY } = fabric.util.qrDecompose(minion.calcTransformMatrix())
      opt.scaleX = scaleX
      opt.scaleY = scaleY
    }
    minion.set(opt)
    minion.setCoords()
  }
}

/**
 * 记录小兵和领主关系
 * @param {fabric.FabricObject} boss
 * @param {fabric.FabricObject[]} minions
 */
const recordRelationship = (boss, minions) => {
  boss.isBoss = true
  if (!boss.id) boss.id = getUniqueBossId()

  const bossTransform = boss.calcTransformMatrix()
  const invertedBossTransform = fabric.util.invertTransform(bossTransform)
  const minionIds = []
  for (const minion of minions) {
    const minionTransform = minion.calcTransformMatrix()
    const desiredTransform = fabric.util.multiplyTransformMatrices(
      invertedBossTransform,
      minionTransform,
    )

    minion.relationship = desiredTransform
    minion.bossId = boss.id
    minion.isMinion = true
    if (!minion.id) minion.id = getUniqueMinionId()
    minionIds.push(minion.id)
  }

  if (Array.isArray(boss.minionIds)) {
    boss.minionIds = [...new Set([...boss.minionIds, ...minionIds])]
  } else {
    boss.minionIds = minionIds
  }
}
/**
 * 从领主手下开除小兵
 * @param {Number[]} minionIds
 * @param {fabric.Canvas} canvas
 */
const unbindMinions = (minionIds, canvas) => {
  const objects = canvas.getObjects()
  const bosses = new Map()
  for (const o of objects) {
    if (o.isMinion && minionIds.some((i) => i === o.id)) {
      const bossId = o.bossId
      const minionId = o.id
      delete o.bossId
      delete o.isMinion
      delete o.id
      delete o.relationship
      globalMinionIds.delete(minionId)
      if (!bosses.has(bossId)) {
        const boss = objects.find((_o) => _o.isBoss && _o.id === bossId)
        if (boss) bosses.set(bossId, boss)
      }
      const boss = bosses.get(bossId)
      if (boss.minionIds.length) {
        boss.minionIds.splice(boss.minionIds.indexOf(minionId), 1)
      }
      if (boss.minionIds.length === 0) {
        delete boss.id
        delete boss.isBoss
        delete boss.minionIds
        globalBossIds.delete(bossId)
      }
    }
  }
}
/**
 * 解除领主职位
 * @param {Number} bossId id
 * @param {fabric.Canvas} canvas
 */
const unbindBoss = (bossId, canvas) => {
  const boss = canvas.getObjects().find((obj) => obj.id === bossId)
  if (!boss) return
  unbindMinions(boss.minionIds, canvas)
  delete boss.isBoss
  delete boss.id
  delete boss.minionIds
  globalBossIds.delete(bossId)
}

/**
 * 移除object时清理关系
 * @param {fabric.FabricObject} object
 * @param {fabric.Canvas} canvas
 */
const removeObject = (object, canvas) => {
  if (object.isBoss) {
    unbindBoss(object.id, canvas)
  } else if (object.isMinion) {
    unbindMinions([object.id], canvas)
  }
  canvas.remove(object)
}

/**
 * 激活小兵追随事件
 * @param {fabric.FabricObject} boss
 * @param {fabric.FabricObject[]} minions
 */
const activateMinions = (boss, minions) => {
  boss.on('moving', () => updateMinions(boss, minions))
  boss.on('rotating', () => updateMinions(boss, minions))
  boss.on('scaling', () => updateMinions(boss, minions))
  for (const minion of minions) {
    minion.on('modified', () => recordRelationship(boss, [minion]))
  }
}

/**
 *
 * @param {fabric.FabricObject} target
 * @param {fabric.Canvas} canvas
 */
function deleteObject(target, canvas) {
  if (target.type.toLowerCase() === 'activeselection') {
    target.forEachObject((obj) => {
      removeObject(obj, canvas)
    })
    canvas.discardActiveObject()
  } else {
    removeObject(target, canvas)
  }
  canvas.requestRenderAll()
}

/**
 * 获取复制对象，返回一个promise，包含对象数组或一个对象
 * @param {fabric.FabricObject | fabric.ActiveSelection} target
 * @return {Promise<fabric.FabricObject[]|fabric.FabricObject>}
 */
async function getClonedObject(target) {
  if (target?.type.toLowerCase() === 'activeselection') {
    const objects = target.getObjects()
    const clonedObjects = []
    const minions = objects.filter((obj) => obj.isMinion)
    const relationships = new Map()
    const ownerlessMinions = new Set(minions)
    for await (const obj of objects) {
      if (obj.isMinion) continue // 跳过，通过boss找minion
      const cloned = await obj.clone()
      clonedObjects.push(cloned)
      if (obj.isBoss) {
        cloned.id = getUniqueBossId()
        relationships.has(cloned) || relationships.set(cloned, [])
        for await (const minion of minions) {
          if (minion.bossId !== obj.id) continue
          const cloneMinion = await minion.clone() // 创建minion的克隆
          cloneMinion.id = getUniqueMinionId()
          relationships.get(cloned).push(cloneMinion)
          clonedObjects.push(cloneMinion)
          ownerlessMinions.delete(minion)
        }
      }
    }
    relationships.forEach((minions, boss) => {
      recordRelationship(boss, minions)
      activateMinions(boss, minions)
    })
    for await (const minion of ownerlessMinions) {
      const cloneMinion = await minion.clone()
      clonedObjects.push(cloneMinion)
    }
    return clonedObjects
  } else {
    const cloned = await target.clone()
    cloned.controls.deleteControl = target.controls.deleteControl
    cloned.controls.cloneControl = target.controls.cloneControl
    return cloned
  }
}
/**
 *
 * @param {fabric.FabricObject} target
 * @param {fabric.Canvas} canvas
 */
async function cloneObject(target, canvas) {
  const cloned = await getClonedObject(target)
  if (Array.isArray(cloned)) {
    canvas.add(...cloned)
    const sel = new fabric.ActiveSelection(cloned)
    sel.left += target.left + 10
    sel.top += target.top + 10
    canvas.setActiveObject(sel)
  } else {
    cloned.left += 10
    cloned.top += 10
    canvas.add(cloned)
    canvas.setActiveObject(cloned)
  }
}

/**
 * 从数组中找到group，获取group中的元素，返回新数组不包含group
 * @param {fabric.FabricObject[]} objs
 */
const flatGroups = (objs) => {
  const result = []
  objs.forEach((o) => {
    if (o.type?.toLowerCase() === 'group') {
      const _objs = o.getObjects() || []
      result.push(...flatGroups(_objs))
    } else {
      result.push(o)
    }
  })
  return result
}

export {
  customProps,
  globalBossIds,
  globalMinionIds,
  getUniqueBossId,
  getUniqueMinionId,
  updateMinions,
  recordRelationship,
  unbindMinions,
  unbindBoss,
  removeObject,
  activateMinions,
  getClonedObject,
  cloneObject,
  deleteObject,
  flatGroups,
}
